﻿using System;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Principal;
using WindowServices.Common.Properties;

namespace WindowServices.Common
{
    /// <summary>
    /// SecurityUtility provide features such as check if certain window user logon is valid
    /// and switch the current thread's security context to different user account.
    /// </summary>
    public class SecurityUtility
    {
        //declare for p/invoke
        [DllImport(@"advapi32.dll")]
        public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
            int dwLogonType, int dwLogonProvider, out IntPtr phToken);

        [DllImport(@"Kernel32.dll")]
        public static extern int GetLastError();

        [DllImport(@"advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public extern static bool DuplicateToken(IntPtr hToken,
            int impersonationLevel,
            ref IntPtr hNewToken);

// ReSharper disable once InconsistentNaming
        private const int LOGON32_LOGON_INTERACTIVE = 2;
// ReSharper disable once InconsistentNaming
        private const int LOGON32_PROVIDER_DEFAULT = 0;
        private const int SecurityImpersonation = 2;
        private readonly WindowsImpersonationContext _impersonationContext = null;

        /// <summary>
        /// This method change the thread's runing-as to the new user account
        /// </summary>
        /// <param name="userName">the user account to switch to</param>
        /// <param name="password"></param>
        /// <param name="domain"></param>
        public void Switch(string userName, string password, string domain)
        {
            IntPtr token;

            //log on as the give user account
            bool loggedOn = LogonUser(
                // User name.
                userName,
                // Computer or domain name.
                domain,
                password,
                LOGON32_LOGON_INTERACTIVE,
                LOGON32_PROVIDER_DEFAULT,
                // The user token for the specified user is returned here.
                out token);

            if (loggedOn == false)
            {
                throw new SecurityException(string.Format(Resources.UserLoginError, userName));
            }
            IntPtr tokenDuplicate = IntPtr.Zero;
            //duplicate the security token
            if (DuplicateToken(token, SecurityImpersonation, ref tokenDuplicate))
            {
                var tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
                //change the current thread's run-as to the new window identity.
                tempWindowsIdentity.Impersonate();
            }
            else
            {
                throw new SecurityException(string.Format(Resources.UserLoginError,userName));
            }
        }
        /// <summary>
        /// this method reverse the thread's run-as to the original user account
        /// </summary>
        public void UndoSwitch()
        {
            _impersonationContext.Undo();
        }

        /// <summary>
        /// Return WindowsIdentity object that represents the user with provided
        /// username/password/domain combination.
        /// </summary>
        /// <param name="userName">user name</param>
        /// <param name="password">user password</param>
        /// <param name="domain">domain or computer name if using local security</param>
        /// <returns></returns>
        public WindowsIdentity LogonUser(string userName, string password, string domain)
        {
            IntPtr token;
            bool loggedOn = LogonUser(
                // User name.
                userName,
                // Computer or domain name.
                domain,
                password,
                LOGON32_LOGON_INTERACTIVE,
                LOGON32_PROVIDER_DEFAULT,
                // The user token for the specified user is returned here.
                out token);

            if (loggedOn == false)
            {
                throw new SecurityException(string.Format(Resources.UserLoginError, userName));
            }
            //create an WindowIdentity object from the newly created token.
            return new WindowsIdentity(token);
        }

        /// <summary>
        /// Check if username/password/domain combination represent
        /// an valid user logon.
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        /// <param name="domain"></param>
        public void ValidateUser(string userName, string password, string domain)
        {
            IntPtr token;
            bool loggedOn = LogonUser(
                // User name.
                userName,
                // Computer or domain name.
                domain,
                // Password.
                password,
                // LOGON32_LOGON_INTERACTIVE .
                LOGON32_LOGON_INTERACTIVE,
                // Logon provider = LOGON32_PROVIDER_DEFAULT.
                LOGON32_PROVIDER_DEFAULT,
                // The user token for the specified user is returned here.
                out token);

            if (loggedOn == false)
            {
                throw new SecurityException(string.Format(Resources.UserLoginError, userName));
            }
        }



    }
}
